/*
 * Copyright (c) 2010, Servoy-stuff
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 * http://www.fsf.org/licensing/licenses/lgpl.txt
 */
package net.stuff.servoy.eclipse.plugin.actions;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintStream;
import java.net.URI;
import java.nio.channels.Channels;
import java.nio.channels.Pipe;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import net.stuff.servoy.eclipse.plugin.Activator;
import net.stuff.servoy.eclipse.plugin.preferences.PreferenceConstants;
import net.stuff.servoy.eclipse.plugin.utils.Main;
import net.stuff.servoy.eclipse.plugin.utils.NotASolutionException;
import net.stuff.servoy.eclipse.plugin.utils.ProgressBarDialog;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionDelegate;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.ui.handlers.IHandlerService;
import org.osgi.framework.Bundle;

/**
 * Main menu action that launches the generation of JSDoc
 *
 * @author Servoy Stuff
 * http://www.servoy-stuff.net/
 *
 */
public class GenerateJSDocAction implements IObjectActionDelegate {
	
	private Shell shell;
	private ISelection selection;
	private Shell dialogShell;
	private File tmpFolder;
	private IWorkbenchPart part;
	private Cursor cursor;
	private String outFolder;
	private LineNumberReader reader;
	private Thread processThread;
	private boolean outExisted;
	private String selectedPath;
	
	/**
	 * Constructor for Action1.
	 */
	public GenerateJSDocAction() {
		super();
	}
	
	/**
	 * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
	 */
	public void setActivePart(IAction action, IWorkbenchPart targetPart) {
		shell = targetPart.getSite().getShell();
		part = targetPart;
	}
	
	/**
	 * @see IActionDelegate#run(IAction)
	 */
	public void run(IAction action) {
		String selected = null;
		try {
			selected = getSelectedFolderPath(selection, true);
		} catch (NotASolutionException ex) {
			MessageBox dialog = new MessageBox(shell, SWT.ERROR | SWT.OK);
			dialog.setText("The current selection is not a Servoy solution or module!");
			dialog.open();
			return;
		}
		selectedPath = selected;
		
		dialogShell = new Shell(shell, SWT.TITLE | SWT.PRIMARY_MODAL);
		final ProgressBarDialog pbd = new ProgressBarDialog(dialogShell);
		outFolder = Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.P_OUT_DIRECTORY);
		final String template = Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.P_STYLES);
		try {
			Bundle bndl = Platform.getBundle("net.stuff.servoy.eclipse.plugin.jsdoc");
			File fl = FileLocator.getBundleFile(bndl);
			final String path = fl.getCanonicalPath();
			tmpFolder = new File(selectedPath+"/temp");
			deleteDir(tmpFolder);
			if (tmpFolder.mkdir()) {
				File sourceFolder = new File(selectedPath);
				final List<File> sourceFiles = getJsFiles(sourceFolder, new ArrayList<File>());
				
				pbd.setMax(sourceFiles.size());
				dialogShell.open();
				
				new Thread("mainThread") {
					public void run() {
						
						try {
							Pipe pipe = Pipe.open();
							reader = new LineNumberReader(new InputStreamReader(Channels.newInputStream(pipe.source())));
							final PrintStream out = new PrintStream(Channels.newOutputStream(pipe.sink())); //new PipedOutputStream(in));
							
							int i = 0;
							for (File file : sourceFiles) {
								final int count = i++;
								final String fileName = file.getName();
								Thread.yield(); //sleep(100);
								dialogShell.getDisplay().asyncExec(new Runnable(){
									public void run() {
										pbd.setLine(fileName);
										pbd.setSelection(count);
									}
								});
								
								if (pbd.isCanceled()) break;
								encapsulateFile(tmpFolder, file);
							}
							
							
							if (!pbd.isCanceled()) {
								File outDir = new File(selectedPath+"/"+outFolder);
								outExisted = (outDir != null && outDir.isDirectory());
								final String[] args = {path+"/app/run.js", tmpFolder.getCanonicalPath(), "-a", "-v", "-t="+path+"/templates/"+template, "-r" , "-d="+selectedPath+"/"+outFolder, "-j="+path+"/app/"};
								
								processThread = new Thread("processThread") {
									public void interrupt() {
										super.interrupt();
										Main.stop();
										if (Main.inputStream != null) {
											try {
												Main.inputStream.close();
											} catch (Exception ex) {}
										}
										if (Main.outputStream != null) {
											try {
												Main.outputStream.close();
											} catch (Exception ex) {}
										}
										if (Main.outputWriter != null) {
											try {
												Main.outputWriter.close();
											} catch (Exception ex) {}
										}
										if (Main.inputReader != null) {
											try {
												Main.inputReader.close();
											} catch (Exception ex) {}
										}
									}
									public void run(){
										try {
											cursor = new Cursor(dialogShell.getDisplay(), SWT.CURSOR_WAIT);
											dialogShell.getDisplay().asyncExec(new Runnable(){
												public void run() {
													dialogShell.setCursor(cursor);
													pbd.setChapter("2/3 - Parsing...");
													pbd.setLine("");
													pbd.setSelection(0);
												}
											});
											Main.setOut(out);
											Main.setErr(System.err);
											Main.exec(args);
										} catch (Exception ex) {
											Activator.getDefault().logError(selectedPath, ex);
										} finally {
											out.flush();
											out.close();
										}
									}
								};
								processThread.start();
							}
						} catch (Exception ex) {
							Activator.getDefault().logError(selectedPath, ex);
						}
						
						if (!pbd.isCanceled()) {
							new Thread("feedbackThread") {
								public void run() {
									try {
										String line = "";
										boolean started = false;
										int min = 0;
										while ((line = reader.readLine()) != null) {
											if (pbd.isCanceled()) {
												try {
													processThread.interrupt();
												} catch (Exception ex) {}
												break;
											}
											if (!started && line.startsWith(" > Parsing")) {
												started = true;
												min = reader.getLineNumber();
											}
											final int minus = min;
											final String[] current = line.split("\\\\");
											if (started) {
												try {
													Thread.sleep(50);
												} catch (InterruptedException ex) {
													Thread.currentThread().interrupt();
													break;
												}
												dialogShell.getDisplay().asyncExec(new Runnable(){
													public void run() {
														int currentLine = reader.getLineNumber() - minus;
														if (currentLine >= pbd.getMax()-1) {
															pbd.setChapter("3/3 - Generating... Hold on!");
															pbd.setLine(" ");
															pbd.setSelection(0);
															pbd.setMayCancel(false);
														} else {
															pbd.setLine(current[current.length-1]);
														}
														pbd.setSelection(currentLine);
													}
												});
											}
										}
									} catch (Exception ex) {
										Activator.getDefault().logError(selectedPath, ex);
									} finally {
										try {
											reader.close();
										} catch (IOException ex) {}
										finish(pbd.isCanceled());
									}
								}
							}.start();
						} else {
							finish(pbd.isCanceled());
						}
					}
				}.start();
			}
		} catch (Exception ex) {
			Activator.getDefault().logError(selectedPath, ex);
		}
		
	}
	
	/**
	 * @param canceled
	 */
	private void finish(final boolean canceled) {
		deleteDir(tmpFolder);
		shell.getDisplay().asyncExec(new Runnable(){
			public void run() {
				try {
					if (cursor != null) cursor.dispose();
					dialogShell.dispose();
					if (!canceled) {
						if (part != null && part instanceof IViewPart) {
							try {
								IHandlerService service = (IHandlerService) part.getSite().getService(IHandlerService.class);
								service.executeCommand("org.eclipse.ui.file.refresh", null);
							} catch (Exception ex) {}
							File out = new File(selectedPath+"/"+outFolder+"/index.html");
							URI uri = out.toURI();
							IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench().getBrowserSupport();
							browserSupport.createBrowser(IWorkbenchBrowserSupport.AS_EDITOR|IWorkbenchBrowserSupport.LOCATION_BAR|IWorkbenchBrowserSupport.NAVIGATION_BAR,null,"JsDoc Reference - Index",uri.toString()).openURL(uri.toURL());
							
						}
					}
				} catch (Exception ex) {}
			}
		});
		if (canceled && !outExisted) {
			File out = new File(selectedPath+"/"+outFolder);
			if (out != null && out.isDirectory()) {
				deleteDir(out);
			}
		}
	}
	
	/**
	 * @param srcFile
	 */
	private static void deleteDir(File srcFile)  { 
		// Checks if file is a directory 
		if (srcFile.isDirectory()) { 
			//Gathers files in directory 
			File[] b = srcFile.listFiles(); 
			for (int i = 0; i < b.length; i++) { 
				//Recursively deletes all files and sub-directories 
				deleteDir(b[i]); 
			} 
			try {
				Thread.sleep(100);
			} catch (InterruptedException ex) {
				Thread.currentThread().interrupt();
			}
			// Deletes original sub-directory file 
			srcFile.delete();
		} else { 
			srcFile.delete(); 
		} 
	}
	
	/**
	 * @see IActionDelegate#selectionChanged(IAction, ISelection)
	 */
	public void selectionChanged(IAction action, ISelection selection) {
		this.selection = selection;
		action.setEnabled(getSelectedFolderPath(selection, false) != null);
	}
		
	/**
	 * @param parent
	 * @param liste
	 * @return
	 */
	public List<File> getJsFiles(File parent, List<File> liste) {
		if (parent.isDirectory()) {
			File out = new File(selectedPath+"/"+outFolder);
			if (!out.equals(parent)) {
				File allFiles[] = parent.listFiles(new FilenameFilter() {
					public boolean accept(File dir, String name) {
						return name.endsWith(".js") || !name.contains(".");
					}
				});
				for(File aFile : allFiles){
					if (aFile.isDirectory()) {
						getJsFiles(aFile, liste);
					} else {
						liste.add(aFile);
					}
				}
			}
		} else {
			liste.add(parent);
		}
		return liste;
	}
	
	/**
	 * @param dest
	 * @param destFiles
	 * @param file
	 * @param fileName
	 * 
	 * @throws IOException
	 */
	private void encapsulateFile(File dest, File file) throws IOException {
		final String fileName = file.getName();
		String function = fileName.substring(0, fileName.length()-3);
		final StringBuffer buffer = new StringBuffer();
		if (!fileName.equalsIgnoreCase("globals.js")) {
			buffer.append(MessageFormat.format("/**\n* @class {0}\n", function));
			if (fileName.endsWith("_calculations.js")) {
				buffer.append("* @see _global_\n");
			}
			buffer.append(MessageFormat.format("*/\nfunction {0}() ", function)); 
			buffer.append("{\n");
		}
		
		BufferedReader inputStream = new BufferedReader(new FileReader(file));
		String inLine = null;
		while ((inLine = inputStream.readLine()) != null) {
			buffer.append(inLine);
			buffer.append("\n");
		}
		if (!fileName.equalsIgnoreCase("globals.js")) {
			buffer.append("}");
		} else {
			buffer.append(" ");
		}
		inputStream.close();
		inputStream = null;
		
		File f = new File(dest.getCanonicalPath()+"/"+fileName);
		if (f.createNewFile()) {
			FileWriter fw = new FileWriter(f);
			fw.write(buffer.toString());
			fw.close();
			fw = null;
		}
	}
	
	/**
	 * @param selection
	 * @return full path 
	 */
	public String getSelectedFolderPath(ISelection selection, boolean canThrow) throws NotASolutionException {
		
		if (selection instanceof IStructuredSelection) {
			Object sel = ((IStructuredSelection) selection).getFirstElement();
			if (sel instanceof IResource) {
				IResource res = (IResource) sel;
				if (res != null)
					return getPath(res, canThrow);
				
			}
			
			if (sel instanceof IAdaptable) {
				IAdaptable ad = (IAdaptable) sel;
				
				IResource resource = (IResource) ad.getAdapter(IResource.class);
				if (resource != null) {
					return getPath(resource, canThrow);
				}
				
			}
			return null;
		}
		return null;
	}
	
	
	/**
	 * @param resource
	 * @param canThrow
	 * @return
	 * @throws NotASolutionException
	 */
	private String getPath(IResource resource, boolean canThrow) throws NotASolutionException {
		IPath loc = resource.getLocation();
		if (loc != null) {
			String path = loc.toOSString();
			File f = new File(path);
			if (f != null && f.isDirectory()) {
				f = new File(path+"/solution_settings.obj");
				if (f != null && f.exists()) {
					return path;
				}
			}
		}
		if (canThrow) {
			throw new NotASolutionException();
		}
		return null;
	}
	
	
}
